package org.jugile.util;

import java.lang.reflect.Method;
import java.util.Map;

import org.jugile.util.Jugile;

/**
 * "And no man in heaven, nor in earth, neither under the earth, was able to open the book,
 *  neither to look thereon. And I wept much, because no man was found worthy to open and 
 *  to read the book, neither to look thereon.  And one of the elders saith unto me, 
 *  Weep not: behold, the Lion of the tribe of Juda, the Root of David, 
 *  hath prevailed to open the book, and to loose the seven seals thereof." (Rev 5:3-5)
 * 
 * ==========
 * 
 * Simple S-expression.
 * @author jukka.rahkonen@iki.fi
 *
 */
public class Expr extends Jugile {

	private String op;
	private Object value;
	private Expr expr;
	private Expr next;
	private Method filterMethod = null;
	private Object[] filterParams = null;
	
	public Expr(String op, Expr e1, Expr e2) {
		this.op = op;
		expr = e1;
		next = e2;
	}
	public Expr(String op, Method filterMethod, Object[] filterParams) {
		this.op = op;
		this.filterMethod = filterMethod;
		this.filterParams = filterParams;
	}
	
	protected Expr(Object atom) { value = atom; }
	private boolean isAtom() { return value != null || filterMethod != null; }

	private Method method;
	private Method method(Object o, String mname) throws Exception {
		if (method != null) return method;
		method = o.getClass().getMethod(mname, null);
		return method;
	}
	
	private Object call(Object o, String mname) throws Exception {
		return method(o,mname).invoke(o, null);
	}

	private boolean test(Object o) throws Exception {
		if (o == null) return false;
		Object res = filterMethod.invoke(o, filterParams);
		if (res == null) return false;
		if (res instanceof Boolean) return (Boolean)res;
		if (res instanceof Integer) return ((Integer)res).intValue() != 0;
		return true; // any not null object is then true		
	}
	
	public boolean eval(Object o) {
		if ("and".equals(op)) return expr.eval(o) && next.eval(o);
		if ("or".equals(op)) return expr.eval(o) || next.eval(o);
		if (filterMethod != null) {
			try {
				boolean res = test(o);
				if ("!".equals(op)) return !res;
				return res;
			} catch(Exception e) { fail(e); }
		}
		Object field = null;
		if (expr.value instanceof String) {
			String mname = (String)expr.value;
			if (o instanceof Map) {
				field = ((Map)o).get(mname);
			} else {
				mname = "get"+up(mname);
				if (o == null) return false;
				try { field = call(o, mname); }
				catch(Exception e) {
					print("error:"+e + " " + mname + " " + o);
					fail(e); 
				}
			}
		} else {
			field = expr.value;
		}
		
		
		Comparable c1 = (Comparable)field;
		Comparable c2 = (Comparable)next.value;
		if (c1 == null) return c2 == null;
		if (c2 == null) return false;
		if (c2 instanceof String) {
			String pat = (String)c2;
			if (pat.endsWith("%")) {
				pat = pat.substring(0, pat.length()-1);
				return (((String)c1).startsWith(pat));
			}
		}
		if ("==".equals(op)) return c1.compareTo(c2) == 0; 
		if ("!=".equals(op)) return c1.compareTo(c2) != 0;			
		if (">".equals(op)) return c1.compareTo(c2) > 0;
		if (">=".equals(op)) return c1.compareTo(c2) >= 0;
		if (">n".equals(op)) {
			if (c1 == null) return true;
			return c1.compareTo(c2) > 0;
		}
		if ("<".equals(op)) return c1.compareTo(c2) < 0;
		if ("<=".equals(op)) return c1.compareTo(c2) <= 0;

		if ("range".equals(op)) {
			// TODO
		}
		fail("unknown op: " + op);
		return false;
	}
	
	public String toString() {
		if (value != null) return value.toString(); // atom
		String res = "(" + op + " " + expr.toString();
		if (next != null) res += " " + next.toString();
		return res + ")";
	}

	public String toSolr() {
		if (value != null) return value.toString();
		if (expr.value != null) return "+"+expr.toSolr()+":"+next.toSolr()+" ";
		return expr.toSolr() + " " + next.toSolr();
	}

	public String toSql() {
		if (value != null) {
			if (value instanceof Time) {
				Time t = (Time)value;
				return t.toSql();
			}
			if (value instanceof IDO) {
				IDO ido = (IDO)value;
				return ido.getId();
			}
			if (value instanceof String) {
				return "'"+value+"'";
			}
			return value.toString();
		}
		if (expr.value != null) return " AND "+getSqlFld((String)expr.value) + " " + getSqlOp()+ " " + next.toSql();
		return expr.toSql() + next.toSql();
	}
	
	protected String getSqlOp() {
		if (">n".equals(op)) fail(">n not supported in sql");
		if ("like".equals(op)) return "LIKE";
		if ("==".equals(op)) return "=";
		return op;
	}
	private String getSqlFld(String name) {
		return name + "_f";
	}

}
